package service

import (
	"context"
	"encoding/json"
	"fmt"
	"math/rand"
	"strconv"
	"time"

	"wxpay/internal/dao"
	"wxpay/internal/dto"
	"wxpay/internal/model"
	"wxpay/pkg/merrors"
	"wxpay/pkg/util"

	"gorm.io/gorm"

	mchApi "wxpay/pkg/wechat_api"
)

type applymentService struct {
	d dao.DataStore
}

func NewApplymentService(d dao.DataStore) *applymentService {
	return &applymentService{d: d}
}

func applyInfoDTOToSubmitRequest(applyDto *dto.ApplymentDTO) mchApi.SubmitApplymentRequest {
	// 必填项
	apply := applyDto.ApplymentInfo
	contactInfo := apply.ContactInfo
	sceneInfo := apply.SalesSceneInfo
	r := mchApi.SubmitApplymentRequest{
		OutRequestNo:     applyDto.ApplyNo,
		OrganizationType: apply.OrganizationType,
		IDDocType:        apply.IDDocType,
		NeedAccountInfo:  apply.NeedAccountInfo,
		ContactInfo: mchApi.ContactInfo{
			ContactType:         contactInfo.ContactType,
			ContactName:         contactInfo.ContactName,
			ContactIDCardNumber: contactInfo.ContactIDCardNumber,
			MobilePhone:         contactInfo.MobilePhone,
			ContactEmail:        contactInfo.ContactEmail,
		},
		SalesSceneInfo: mchApi.SalesSceneInfo{
			StoreName:           sceneInfo.StoreName,
			StoreUrl:            sceneInfo.StoreUrl,
			StoreQrCode:         sceneInfo.StoreQrCode,
			MiniProgramSubAppID: sceneInfo.MiniProgramSubAppID,
		},
		MerchantShortname: apply.MerchantShortname,
	}
	// 补充营业执照、登记证书信息
	bliDTO := apply.BusinessLicenseInfo
	switch apply.OrganizationType {
	case "2", "3", "4", "1708":
		r.BusinessLicenseInfo = &mchApi.BusinessLicenseInfo{
			BusinessLicenseCopy:   bliDTO.BusinessLicenseCopy,
			BusinessLicenseNumber: bliDTO.BusinessLicenseNumber,
			MerchantName:          bliDTO.MerchantName,
			LegalPerson:           bliDTO.LegalPerson,
			CompanyAddress:        bliDTO.CompanyAddress,
			BusinessTime:          bliDTO.BusinessTime,
		}
	}
	// 补充组织机构代码证信息
	if orgCert := apply.OrganizationCertInfo; orgCert != nil {
		r.OrganizationCertInfo = &mchApi.OrganizationCertInfo{
			OrganizationCopy:   orgCert.OrganizationCopy,
			OrganizationNumber: orgCert.OrganizationNumber,
			OrganizationTime:   orgCert.OrganizationTime,
		}
	}
	// 补充身份证信息
	if idCard := apply.IDCardInfo; idCard != nil {
		r.IDCardInfo = &mchApi.IDCardInfo{
			IDCardCopy:      idCard.IDCardCopy,
			IDCardNational:  idCard.IDCardNational,
			IDCardName:      idCard.IDCardName,
			IDCardNumber:    idCard.IDCardNumber,
			IDCardValidTime: idCard.IDCardValidTime,
		}
	}
	// 补充法人其他证件信息
	if idDoc := apply.IDDocInfo; idDoc != nil {
		r.IDDocInfo = &mchApi.IDDocInfo{
			IDDocName:    idDoc.IDDocName,
			IDDocNumber:  idDoc.IDDocNumber,
			IDDocCopy:    idDoc.IDDocCopy,
			DocPeriodEnd: idDoc.DocPeriodEnd,
		}
	}
	// 补充结算账户信息
	if account := apply.AccountInfo; apply.NeedAccountInfo && account != nil {
		r.AccountInfo = &mchApi.AccountInfo{
			BankAccountType: account.BankAccountType,
			AccountBank:     account.AccountBank,
			AccountName:     account.AccountName,
			BankAddressCode: account.BankAddressCode,
			BankBranchID:    account.BankBranchID,
			BankName:        account.BankName,
			AccountNumber:   account.AccountNumber,
		}
	}
	if len(apply.Qualifications) > 0 {
		r.Qualifications = apply.Qualifications
	}
	if len(apply.BusinessAdditionPics) > 0 {
		r.BusinessAdditionPics = apply.BusinessAdditionPics
	}
	switch apply.OrganizationType {
	case "2500":
		r.BusinessAdditionDesc = "该商户已持续从事电子商务经营活动满6个月，且期间经营收入累计超过20万元。"
	default:
		r.BusinessAdditionDesc = apply.BusinessAdditionDesc
	}
	return r
}

func applyInfoDTOToQueryRequest(applyDto *dto.ApplymentDTO) mchApi.QueryApplymentByOutRequestNoRequest {
	r := mchApi.QueryApplymentByOutRequestNoRequest{OutRequestNo: applyDto.ApplyNo}
	return r
}

type clientInterface interface {
	applymentSubmit(ctx context.Context, client mchApi.MerchantApiClient, dto *dto.ApplymentDTO) error
	applymentQuery(ctx context.Context, client mchApi.MerchantApiClient, dto *dto.ApplymentDTO) (*dto.ApplymentDTO, error)
}

func (s *applymentService) getClient(applyType string) (clientInterface, error) {
	if applyType == "ecommerce" {
		//电商收付通进件
		return EcommerceClient{s}, nil
	}
	return nil, fmt.Errorf("applyType: %s not supported", applyType)
}

type EcommerceClient struct {
	service *applymentService
}

// 电商收付通进件申请
func (ec EcommerceClient) applymentSubmit(ctx context.Context, client mchApi.MerchantApiClient, apiDto *dto.ApplymentDTO) error {
	// dto 转换成request
	req := applyInfoDTOToSubmitRequest(apiDto)
	// 调用进件接口
	_, err := client.ApplymentSubmit(ctx, req)
	if err != nil {
		if val, ok := err.(model.Idempotent); ok && val.IsIdempotent() {
			// 幂等错误视为成功
			return nil
		}
		return err
	}
	return nil
}

// 电商收付通进件查询
func (ec EcommerceClient) applymentQuery(ctx context.Context, client mchApi.MerchantApiClient, apiDto *dto.ApplymentDTO) (*dto.ApplymentDTO, error) {
	// dto 转换成request
	req := applyInfoDTOToQueryRequest(apiDto)
	// 调用查询接口
	resp, err := client.ApplymentQueryByOutRequestNo(ctx, req)
	if err != nil {
		return nil, err
	}
	apiDto.WxApplymentID = strconv.Itoa(int(resp.ApplymentID))
	apiDto.ApplyState = resp.ApplymentState
	apiDto.ApplyStateDesc = resp.ApplymentStateDesc
	apiDto.SignURL = resp.SignUrl
	apiDto.SubMchID = resp.SubMchID
	apiDto.LegalValidationURL = resp.LegalValidationUrl
	account := resp.AccountValidation
	if account.AccountName != "" {
		apiDto.AccountValidation = &dto.AccountValidationDTO{
			AccountName:              account.AccountName,
			AccountNo:                account.AccountNo,
			PayAmount:                account.PayAmount,
			DestinationAccountNumber: account.DestinationAccountNumber,
			DestinationAccountName:   account.DestinationAccountName,
			DestinationAccountBank:   account.DestinationAccountBank,
			City:                     account.City,
			Remark:                   account.Remark,
			Deadline:                 account.Deadline,
		}
	}
	audits := resp.AuditDetail
	if len(audits) > 0 {
		ads := make(dto.AuditDetailDTO, len(audits))
		for i, d := range audits {
			ads[i] = struct {
				ParamName    string
				RejectReason string
			}{ParamName: d.ParamName, RejectReason: d.RejectReason}
		}
		apiDto.AuditDetail = ads
	}
	return apiDto, nil
}

func GenApplyNo() string {
	//	申请编号生成规则 A+时间+8位随机数
	t := util.GetNow()
	s := rand.NewSource(t.UnixNano())
	r := rand.New(s)
	return "A" + t.Format(util.LayoutyyMMddHHmmss) + fmt.Sprintf("%08d", r.Intn(100000000))
}

// 提交申请
func (s *applymentService) Submit(ctx context.Context, apply *dto.ApplymentDTO) (*dto.ApplymentDTO, error) {
	// 幂等检查
	r, fErr := s.d.Applyment().QueryByBizApplyNo(ctx, s.d.GetTx(ctx), apply.BizApplyNo)
	if fErr == nil {
		// 已存在申请单，直接返回
		return dto.ConvertApplyment(r), nil
	}
	if val, ok := fErr.(model.NotFound); ok && val.NotExist() {
		// 不存在申请单则需要创建
		// 生成申请编号
		apply.ApplyNo = GenApplyNo()
		return s.submitApplyment(ctx, apply)
	}
	return nil, fErr
}

// 修改申请后重新提交
func (s *applymentService) ReSubmit(ctx context.Context, apply *dto.ApplymentDTO) (*dto.ApplymentDTO, error) {
	// 幂等检查
	const op merrors.Op = "applymentService.ReSubmit"
	r, fErr := s.d.Applyment().QueryByBizApplyNo(ctx, s.d.GetTx(ctx), apply.BizApplyNo)
	if val, ok := fErr.(model.NotFound); ok && val.NotExist() {
		// 不存在申请单则报错
		return nil, merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "找不到申请单")
	}
	if fErr != nil {
		return nil, fErr
	}
	if !r.IsFinalState() || !r.CanReApply() {
		// 非终态或者不予许重新提交
		return nil, merrors.E(op, merrors.Invalid, merrors.ErrStatusInValid, "状态不允许重新提交")
	}
	// 已存在申请单，在这个基础上重新提交
	apply.ID = r.ID
	apply.ApplyNo = r.ApplyNo
	return s.submitApplyment(ctx, apply)
}

func (s *applymentService) submitApplyment(ctx context.Context, apply *dto.ApplymentDTO) (*dto.ApplymentDTO, error) {
	const op merrors.Op = "applymentService.Submit"
	m := dto.AdaptApplyment(apply)
	// 校验申请资料的业务完整性
	if err := m.Validate(); err != nil {
		return nil, merrors.E(op, merrors.Invalid, merrors.ErrInvalidArguments, err)
	}

	// 调用client api进件
	apiClient, err := NewMchService(s.d).BuildMchApiClient(ctx, apply.MchID)
	if err != nil {
		return nil, err
	}
	cli, err := s.getClient(apply.ApplymentType)
	if err != nil {
		return nil, err
	}
	// 提交进件申请
	err = cli.applymentSubmit(ctx, apiClient, apply)
	if err != nil {
		return nil, err
	}
	// 查询进件申请详情
	applyDto, err := cli.applymentQuery(ctx, apiClient, apply)
	if err != nil {
		return nil, err
	}
	// 申请结果持久化
	applyment := dto.AdaptApplyment(applyDto)
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		if e := s.d.Applyment().Save(ctx, tx, applyment); e != nil {
			return e
		}
		if e := s.createApplymentQueryTask(ctx, tx, applyment); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return nil, err
	}
	return applyDto, nil
}

// 查询详情
func (s *applymentService) Query(ctx context.Context, brandID string, applyNo string, queryExternal bool) (*dto.ApplymentDTO, error) {
	const op merrors.Op = "applymentService.Query"
	r, err := s.d.Applyment().QueryByApplyNo(ctx, s.d.GetTx(ctx), applyNo)
	if err != nil {
		return nil, err
	}
	if r.BrandID != brandID {
		return nil, merrors.E(op, merrors.Permission, merrors.ErrPermission, "没有权限查看此订单")
	}
	// 非终态需要查询微信侧进件状态
	if !r.IsFinalState() && queryExternal {
		r, err = s.queryApplymentAndSyncState(ctx, r, false)
		if err != nil {
			return nil, err
		}
	}

	return dto.ConvertApplyment(r), nil
}

// 执行进件查询任务
func (s *applymentService) ExecuteQueryTask(ctx context.Context, task *model.ApplymentQueryTask) (err error) {
	repo := s.d.Applyment()
	m, err := repo.QueryByApplyNo(ctx, s.d.GetTx(ctx), task.ApplyNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 不存在直接跳过
		return nil
	}
	if err != nil {
		return
	}
	if m.IsFinalState() {
		// 终态直接返回
		return nil
	}
	s.queryApplymentAndSyncState(ctx, m, true)
	return
}

func (s *applymentService) queryApplymentAndSyncState(ctx context.Context, m *model.Applyment, isLongTerm bool) (apply *model.Applyment, err error) {
	// 调用进件查询接口并更新状态
	const op merrors.Op = "applymentService.queryApplymentAndSyncState"
	repo := s.d.Applyment()
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		applyment, e := s.d.Lock().LockApplyment(ctx, tx, m.ID)
		if e != nil {
			return e
		}

		client, e := NewMchService(s.d).BuildMchApiClient(ctx, applyment.MchID)
		if e != nil {
			return merrors.E(op, merrors.Invalid, e)
		}
		req := mchApi.QueryApplymentByOutRequestNoRequest{OutRequestNo: applyment.ApplyNo}
		resp, e := client.ApplymentQueryByOutRequestNo(ctx, req)
		if e != nil {
			return merrors.E(op, merrors.Invalid, merrors.ErrWxApi, e)
		}
		accountV, accountD := buildValidationAndAudit(resp)
		if applyment.IsNewState(resp.ApplymentState) {
			applyment.UpdateState(resp.ApplymentState, resp.ApplymentStateDesc, resp.SignUrl, resp.SubMchID, accountV, accountD)
			if e := repo.Save(ctx, tx, applyment); e != nil {
				return e
			}
			if e := s.createApplymentNotification(ctx, tx, applyment); e != nil {
				return e
			}
		}
		apply = applyment
		if !applyment.IsFinalState() && isLongTerm {
			// 非终态且来自定时任务触发（isLongTerm=true），需要另起定时任务查询
			if e := s.createApplymentQueryTask(ctx, tx, applyment); e != nil {
				return e
			}
		}
		return nil
	}); err != nil {
		return
	}
	return
}

func (s *applymentService) createApplymentQueryTask(ctx context.Context, tx *gorm.DB, apply *model.Applyment) error {
	payload := model.NewApplymentQueryTask(apply)
	ps, _ := json.Marshal(&payload)
	// 延后1个小时执行
	msg := model.NewMessage(model.TopicApplymentQuery, ps, time.Hour)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

func (s *applymentService) createApplymentNotification(ctx context.Context, tx *gorm.DB, apply *model.Applyment) error {
	payload := model.NewApplymentNotification(apply)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicApplymentNotify, ps, 0)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

func buildValidationAndAudit(resp *mchApi.ApplymentQueryResponse) (accountV *model.AccountValidation, auditD *model.AuditDetails) {
	av := resp.AccountValidation
	var audits model.AuditDetails
	switch resp.ApplymentState {
	case "ACCOUNT_NEED_VERIFY":
		accountV = &model.AccountValidation{
			AccountName:              av.AccountName,
			AccountNo:                av.AccountNo,
			PayAmount:                av.PayAmount,
			DestinationAccountNumber: av.DestinationAccountNumber,
			DestinationAccountName:   av.DestinationAccountName,
			DestinationAccountBank:   av.DestinationAccountBank,
			City:                     av.City,
			Remark:                   av.Remark,
			Deadline:                 av.Deadline,
		}
	case "REJECTED", "FROZEN":
		for _, d := range resp.AuditDetail {
			audits = append(audits, model.AuditDetail{
				ParamName:    d.ParamName,
				RejectReason: d.RejectReason,
			})
		}
	}
	auditD = &audits
	return
}
